/*
 * Copyright (c) 2018-2024 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "subcmd.h"

#define NAME_LEN 10
#define STAT_LEN 7
#define RM_LEN   6
#define UT_LEN   6
#define PG_LEN   6
#define TMP_LEN  6
#define MEM_LEN  6
#define SWP_LEN  6

static unsigned int sintNumLoadIndex;
static char **sarrayLoadIndex;

static json *nodeload_2json(struct QuickPoolHostLoad *, struct QuickPoolResourceReply *,
                            struct quickpoolInfo *);
static void nodeload_usage(const char *);
static int compare_load(const void *x, const void *y);

/**
 * @brief       qp node load handler, to query node load from jm.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to query node load
 * @retval      4             #5: not enough memory to print json string
 */
int
cmd_node_load(int argc, char **argv)
{
    int options = 0;
    unsigned int numNeeded = 0;
    char *strResReq = NULL, *strIndexFilter = NULL;
    bool_t fOpt = FALSE;
    bool_t wOpt = FALSE;
    
    while (TRUE) {
        int option_index = 0;
        static struct option long_options[] = {
            {"help", no_argument, 0, 'h'},
            {"requirement", required_argument, 0, 'R'},
            {"indexfilter", required_argument, 0, 'I'},
            {"normalize", no_argument, 0, 'N'},
            {"number", required_argument, 0, 'n'},
            {"format", no_argument, 0, 'f'},
            {"wide", no_argument, 0, 'w'},
            {NULL, 0, 0, 0}
        };

        int opt;
        opt = getopt_long(argc, argv, "R:I:Nn:h:fw", long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'R':
            if (PATH_MAX < strlen(optarg)) {
                fprintf(stderr, "The resource requirement string length is longer than %d.\n",
                        PATH_MAX);
                exit(1);
            }

            strResReq = optarg;
            break;
        case 'I':
            if (fOpt) {
                fprintf(stderr, "-I/--indexfilter cannot be used with -f/--format at the same time.\n");
                nodeload_usage(argv[0]);
                exit(1);
            }
            if (wOpt) {
                fprintf(stderr, "-I/--indexfilter cannot be used with -w/--wide at the same time.\n");
                nodeload_usage(argv[0]);
                exit(1);
            }
            strIndexFilter = optarg;
            break;
        case 'N':
            options = NORMALIZE;
            break;
        case 'n':
            numNeeded = atoi(optarg);
            if (numNeeded <= 0) {
                fprintf(stderr, "Must specify a positive value for -n/--number option.\n");
                nodeload_usage(argv[0]);
                exit(2);
            }

            break;
        case 'f':
            if (strIndexFilter != NULL) {
                fprintf(stderr, "-f/--format cannot be used with -I/--indexfilter at the same time.\n");
                nodeload_usage(argv[0]);
                exit(1);
            }
            fOpt = TRUE;
            break;
        case 'w':
            if (strIndexFilter != NULL) {
                fprintf(stderr, "-w/--wide cannot be used with -I/--indexfilter at the same time.\n");
                nodeload_usage(argv[0]);
                exit(1);
            }
            wOpt = TRUE;
            break;
        case 'h':
            nodeload_usage(argv[0]);
            exit(0);
        case '?':
        default:
            nodeload_usage(argv[0]);
            exit(1);
        }
    }

    // if not specify node name, set all to TRUE
    int all=FALSE;
    unsigned int numNode;
    char **nodeloads=NULL, **nodeloadPoint;
    numNode = get_argument_names(argc, argv, optind, &nodeloads, &all, "host");
    if (all || 0 == numNode) {
        nodeloadPoint = NULL;
    } else {
        nodeloadPoint = nodeloads;
    }

    struct quickpoolInfo *qpInfo=NULL;
    qpInfo = qp_info(NULL);
    if (NULL == qpInfo) { // failed to query quickpool base info.
        exit(3);
    }

    if (NULL == strIndexFilter) {
        strIndexFilter = (char *)"r1m:r5m:r15m:ut:pg:tmp:swp:mem";
    }

    if (NULL == strResReq) {
        strResReq = (char *)"-";
    }

    struct request loadRequest;
    memset(&loadRequest, 0, sizeof(struct request));

    int n;
    if (0 < numNode) {
        n = 5;
    } else {
        n = 3;
    }

    loadRequest.keyvalues = (struct keyvalue *)calloc(n, sizeof(struct keyvalue));
    if (NULL == loadRequest.keyvalues) {
        fprintf(stderr, "calloc(%ld) failed due to %m.\n",
                n*sizeof(struct keyvalue));
        exit(4);
    }

    n=0;
    loadRequest.keyvalues[n].key = REQUEST_KEY_OPTIONS;
    loadRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
    if (NULL == loadRequest.keyvalues[n].value) {
        fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
        exit(4);
    }
    snprintf(loadRequest.keyvalues[n].value, 12, "%d", options);
    n ++;

    if (0 < numNode) {

        // to query specific nodes.
        loadRequest.keyvalues[n].key = REQUEST_KEY_NUMBER;
        loadRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
        if (NULL == loadRequest.keyvalues[n].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(loadRequest.keyvalues[n].value, 12, "%d", numNode);
        n++;

        loadRequest.keyvalues[n].key = REQUEST_KEY_NAMES;
        unsigned int i, size = 0;
        for (i = 0; i < numNode; i++) {
            size += strlen(nodeloadPoint[i]) + 1;
        }
        loadRequest.keyvalues[n].value = (char *)calloc(size+1, sizeof(char));
        if (NULL == loadRequest.keyvalues[n].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", size);
            exit(4);
        }

        for (i = 0; i < numNode; i++) {

            if (i == 0) {
                strncpy(loadRequest.keyvalues[n].value, nodeloadPoint[i], size);
            } else {
                strncat(loadRequest.keyvalues[n].value, " ", size);
                strncat(loadRequest.keyvalues[n].value, nodeloadPoint[i], size);
            }
        }
        n ++;
    }

    if (0 < numNeeded) { // number of node I need from JM.
        loadRequest.keyvalues[n].key = REQUEST_KEY_NUMBER_NEED;
        loadRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
        if (NULL == loadRequest.keyvalues[n].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(loadRequest.keyvalues[n].value, 12, "%d", numNeeded);
        n ++;
    }

    unsigned int len;
    len = strlen(strResReq) + strlen(strIndexFilter) + strlen("filter[]") + 5;

    char *finalResReq;
    finalResReq = (char *)calloc(len+1, sizeof(char));
    if (NULL == finalResReq) {
        fprintf(stderr, "calloc(%d) failed due to %m.\n", len);
        exit(4);
    }
    snprintf(finalResReq, len, "%s filter[%s]", strResReq, strIndexFilter);
    loadRequest.keyvalues[n].key = REQUEST_KEY_RESREQ;
    loadRequest.keyvalues[n].value = copy_string(finalResReq);
    n ++;

    loadRequest.number = n;

    quickpool_errno = 0;
    struct QuickPoolHostLoadReply *loadReply;
    loadReply = qp_gethostload(&loadRequest, NULL);
    if (NULL == loadReply || quickpool_errno != 0) {

        if (NULL != loadReply) {

            // JM replied error message.
            fprintf(stderr, "%s.\n", loadReply->error_message);
            FREEUP(loadReply);
        } else {
            fprintf(stderr, "%s\n", "Failed to query node info due to above reason.");
        }

        gf_xdr_free(gf_xdr_request, &loadRequest);
        FREEUP(nodeloads);

        exit(3);
    }
    gf_xdr_free(gf_xdr_request, &loadRequest);
    FREEUP(nodeloads);

    sintNumLoadIndex = loadReply->num_index;
    sarrayLoadIndex = loadReply->indicies;

    quickpool_errno = 0;
    struct request resourceRequest;
    memset(&resourceRequest, 0, sizeof(struct request));

    resourceRequest.number = 1;
    resourceRequest.keyvalues = (struct keyvalue *)calloc(1, sizeof(struct keyvalue));
    if (NULL == resourceRequest.keyvalues) {
        fprintf(stderr, "calloc(%ld) failed due to %m.\n", sizeof(struct keyvalue));
        exit(4);
    }

    resourceRequest.keyvalues[0].key = REQUEST_KEY_OPTIONS;
    resourceRequest.keyvalues[0].value = (char *)calloc(12, sizeof(char));
    if (NULL == resourceRequest.keyvalues[0].value) {
        fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
        exit(4);
    }
    snprintf(resourceRequest.keyvalues[0].value, 12, "%d", 0);

    struct QuickPoolResourceReply *resourceReply=NULL;
    resourceReply = qp_sharedresourceinfo(&resourceRequest, NULL);
    if (NULL == resourceReply || quickpool_errno != 0) {

        if (NULL != resourceReply && quickpool_errno != ERROR_NO_RESOURCE) {

            // JM replied error message.
            fprintf(stderr, "%s\n", resourceReply->error_message);

            gf_xdr_free(gf_xdr_request, (void *)&resourceRequest);
            gf_xdr_free(libxdr_load_reply, loadReply);
            FREEUP(resourceReply);

            exit(2);
        }
    }
    gf_xdr_free(gf_xdr_request, (void *)&resourceRequest);

    if (0 == loadReply->num_hosts) {
        fprintf(stderr, "No load info from the cluster.\n");
        exit(0);
    }

    qsort((struct QuickPoolHostLoad *)loadReply->host_load, loadReply->num_hosts, sizeof(struct QuickPoolHostLoad), compare_load);
    
    /*option -f or --format args*/
    char buf[4096+1] = {0};
    char header[4096+1] = {0};
    char strStatus[20] = {0};
    char val_info[8][20];  /*the same number as the number of strIndexFilter */
    unsigned int i, k;

    if ((fOpt|wOpt) && loadReply->num_hosts > 0){
        
        memset(val_info, 0, sizeof(val_info));
        snprintf(buf, 4096, "%%-%ds  %%-%ds  %%%ds  %%%ds  %%%ds  %%%ds  %%%ds  %%%ds  %%%ds  %%%ds\n",
                 NAME_LEN, STAT_LEN, RM_LEN, RM_LEN, RM_LEN, UT_LEN, PG_LEN, TMP_LEN, MEM_LEN, SWP_LEN);
        snprintf(header, 4096, "%%-%ds  %%-%ds  %%%ds  %%%ds  %%%ds  %%%ds  %%%ds  %%%ds  %%%ds  %%%ds\n",
                 NAME_LEN, STAT_LEN, RM_LEN, RM_LEN, RM_LEN, UT_LEN, PG_LEN, TMP_LEN, MEM_LEN, SWP_LEN);
        fprintf(stdout,header, "NAME", "STAT", "r1m", "r5m", "r15m", "ut", "pg", "tmp", "mem", "swp");
           
        for (i = 0; i < loadReply->num_hosts; i++){
            
            if (STATUS_ISUNAVAIL(loadReply->host_load[i].status)) {
                strcpy(strStatus, "unavail");
                fprintf(stdout, buf, loadReply->host_load[i].host_name, strStatus, "-", "-", "-",
                "-", "-", "-", "-", "-");
                continue;
            } else {
                strStatus[0] = '\0';

                if (STATUS_ISBUSY(loadReply->host_load[i].status)) {
                    strcat(strStatus, "busy");
                } else {
                    strcat(strStatus, "ok");
                }
            }
            
            for (k = 0; k < sintNumLoadIndex; k++) {

                char *busy;
                if (NODE_ISBUSYON(loadReply->host_load[i].status, k)) {
                    busy = (char *)"*";
                } else {
                    busy = EMPTY_STRING;
                }
                
                char strLoad[56+1];
                if (0 == strcmp(sarrayLoadIndex[k], "mem") || 0 == strcmp(sarrayLoadIndex[k], "swp")
                    || 0 == strcmp(sarrayLoadIndex[k], "tmp")) {
                
                    char *strUnit;
                    float loadVal;
                    if (loadReply->host_load[i].load_values[k] > 1024*1024) {
                        loadVal = loadReply->host_load[i].load_values[k]/(double)(1024*1024);
                        strUnit = (char *)"T";
                    } else if (loadReply->host_load[i].load_values[k] > 1024) {
                        loadVal = loadReply->host_load[i].load_values[k]/(double)(1024);
                        strUnit = (char *)"G";
                    } else {
                        loadVal = loadReply->host_load[i].load_values[k];
                        strUnit = (char *)"M";
                    }
                
                    snprintf(strLoad, 56, "%s%.1f%s", busy, loadVal, strUnit);
                } else {
                    char *busy;
                    if (NODE_ISBUSYON(loadReply->host_load[i].status, k)) {
                        busy = (char *)"*";
                    } else {
                        busy = EMPTY_STRING;
                    }
                
                    snprintf(strLoad, 56, "%s%.2f", busy, loadReply->host_load[i].load_values[k]);
                }
                
                strcpy(val_info[k], strLoad);
            }
            
            if (fOpt && !wOpt && strlen(loadReply->host_load[i].host_name) > NAME_LEN){
                loadReply->host_load[i].host_name[NAME_LEN-1] = '*';
                loadReply->host_load[i].host_name[NAME_LEN] = '\0';
            } 
            
            if (fOpt && !wOpt && strlen(strStatus) > STAT_LEN){
                strStatus[STAT_LEN-1] = '*';
                strStatus[STAT_LEN] = '\0';
            } 
            
            fprintf(stdout, buf, loadReply->host_load[i].host_name,
                    strStatus, val_info[0], val_info[1], val_info[2],
                    val_info[3], val_info[4], val_info[5], val_info[7], val_info[6]);
        }
        
        FREEUP(resourceReply);
        FREEUP(loadReply);
        
        exit(0);
    }

    json *jsonLoadList = json_CreateObject();
    for (i=0; i<loadReply->num_hosts; i++) {

        json *jsonLoad = nodeload_2json(&(loadReply->host_load[i]), resourceReply, qpInfo);
        json_AddItemToObject(jsonLoadList, loadReply->host_load[i].host_name, jsonLoad);
    }


    char *strJson;
    strJson = json_Print(jsonLoadList);
    if (NULL != strJson) {
        fprintf(stdout, "%s\n", strJson);
        FREEUP(strJson);
    } else {

        perror("malloc");
        json_Delete(jsonLoadList);
        gf_xdr_free(libxdr_resource_reply, resourceReply);
        FREEUP(resourceReply);
        gf_xdr_free(libxdr_load_reply, loadReply);
        FREEUP(loadReply);

        exit(4);
    }
    json_Delete(jsonLoadList);

    gf_xdr_free(libxdr_resource_reply, resourceReply);
    FREEUP(resourceReply);
    gf_xdr_free(libxdr_load_reply, loadReply);
    FREEUP(loadReply);

    exit(0);
} // end function cmd_node_load

/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

/**
 * @brief       transform node load structure to json.
 *
 * @param[in]   load          #1: node load to be tranformed
 * @param[in]   sharedres     #2: global shared resource
 * @param[in]   info          #3: load index and resource table
 *
 * @retval      not NULL      #1: transformed json object
 *
 * @note        if host is unavail, do not transfer static load
 */
static json *
nodeload_2json(struct QuickPoolHostLoad *load, struct QuickPoolResourceReply *sharedres,
               struct quickpoolInfo *info)
{
    json *jsonLoad = json_CreateObject();

    s2j_json_set_basic_element(jsonLoad, load, string, "HOST_NAME", host_name);

    char strStatus[20];
    if (STATUS_ISUNAVAIL(load->status)) {
        strcpy(strStatus, "unavail");
    } else {
        strStatus[0] = '\0';

        if (STATUS_ISBUSY(load->status)) {
            strcat(strStatus, "busy");
        } else {
            strcat(strStatus, "ok");
        }
    }

    json_AddStringToObject(jsonLoad, "STATUS", strStatus);

    if (STATUS_ISUNAVAIL(load->status)) {
        return jsonLoad;
    }

    json *jsonResValue;
    jsonResValue = json_CreateObject();

    unsigned int i;
    for (i=0; i<sintNumLoadIndex; i++) {

        char *busy;
        if (NODE_ISBUSYON(load->status, i)) {
            busy = (char *)"*";
        } else {
            busy = EMPTY_STRING;
        }

        char strLoad[56+1];
        if (0 == strcmp(sarrayLoadIndex[i], "mem") || 0 == strcmp(sarrayLoadIndex[i], "swp")
            || 0 == strcmp(sarrayLoadIndex[i], "tmp")) {

            char *strUnit;
            float loadVal;
            if (load->load_values[i] > 1024*1024) {
                loadVal = load->load_values[i]/(double)(1024*1024);
                strUnit = (char *)"T";
            } else if (load->load_values[i] > 1024) {
                loadVal = load->load_values[i]/(double)(1024);
                strUnit = (char *)"G";
            } else {
                loadVal = load->load_values[i];
                strUnit = (char *)"M";
            }

            snprintf(strLoad, 56, "%s%.1f%s", busy, loadVal, strUnit);
            json_AddStringToObject(jsonResValue, sarrayLoadIndex[i], strLoad);
        } else {
            char *busy;
            if (NODE_ISBUSYON(load->status, i)) {
                busy = (char *)"*";
            } else {
                busy = EMPTY_STRING;
            }

            snprintf(strLoad, 56, "%s%.2f", busy, load->load_values[i]);
            json_AddStringToObject(jsonResValue, sarrayLoadIndex[i], strLoad);
        }
    }

    if (NULL == sharedres || 0 == sharedres->num_resources) {

        json_AddItemToObject(jsonLoad, "LOAD", jsonResValue);
        return jsonLoad;
    }

    unsigned int j, k;
    for (i=0; i<sharedres->num_resources; i++) {

        bool_t found = FALSE;
        for (j=0; j<info->num_resources; j++) {
            if (strcmp(info->resource_table[j].name, sharedres->resources[i].resource_name) != 0) {
                continue;
            }

            if (info->resource_table[j].flags & RESOURCE_FLAG_DYNAMIC) {
                found = TRUE;
            }

            break;
        }

        if (!found) {
            continue;
        }

        for (j=0; j<sharedres->resources[i].num_instances; j++) {

            struct QuickPoolResourceMap *resMap = &(sharedres->resources[i].instances[j]);
            strip_spaces(resMap->total_value, FALSE);
            strip_spaces(resMap->reserve_value, FALSE);

            for (k=0; k<resMap->num_hosts; k++) {

                if (strcmp(resMap->hosts[k], ALL_STRING) == 0) {

                    s2j_json_set_basic_element(jsonResValue, resMap, string,
                                               sharedres->resources[i].resource_name, total_value);
                    break;
                }

                if (strcmp(resMap->hosts[k], load->host_name) == 0) {

                     s2j_json_set_basic_element(jsonResValue, resMap, string,
                                                sharedres->resources[i].resource_name, total_value);
                    break;
                }
            }
        }
    }

    json_AddItemToObject(jsonLoad, "LOAD", jsonResValue);

    return jsonLoad;
} // end function nodeload_2json

static void
nodeload_usage(const char *cmd)
{
    fprintf(stdout, "qp node %s usage: [-h, --help] [-N, --normalize] [-R, --requirement res_req]\n", cmd);
    fprintf(stdout, "\t\t    [-f, --format] [-w, --wide] | [-I, --indexfilter index_list]\n");
    fprintf(stdout, "\t\t    [-n, -- num_hosts] [host_name ...]\n");

    return;
}

static int
compare_load(const void *x, const void *y)
{
    struct QuickPoolHostLoad *node1;
    struct QuickPoolHostLoad *node2;
    node1 = (struct QuickPoolHostLoad *)x;
    node2 = (struct QuickPoolHostLoad *)y;

    if (STATUS_ISUNAVAIL(node1->status) && STATUS_ISUNAVAIL(node2->status)) {
        return strcmp(node1->host_name, node2->host_name);
    } else if (STATUS_ISUNAVAIL(node1->status) && !STATUS_ISUNAVAIL(node2->status)) {
        return 1;
    } else if (!STATUS_ISUNAVAIL(node1->status) && STATUS_ISUNAVAIL(node2->status)) {
        return -1;
    }

    if (node1->load_values[0] < node2->load_values[0]) {
        return -1;
    } else if (node1->load_values[0] > node2->load_values[0]) {
        return 1;
    }
    return strcmp(node1->host_name, node2->host_name);
}
